/**
 * gnome-gmail-notifier: the gnome gmail notifier.
 * Copyright (C) 2007 Bradley A. Worley.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the
 * 
 *     Free Software Foundation, Inc.
 *     59 Temple Place, Suite 330
 *     Boston, MA 02111-1307 USA
 **/

/*
 * include our application header.
 */
#include <main.h>

/*
 * type definitions.
 */
typedef struct _GgnPresenceAudio GgnPresenceAudio;

/*
 * gstreamer audio information struct.
 */
struct _GgnPresenceAudio {
    /* our audio loop. */
    GMainLoop* loop;
    
    /* our gstreamer pipeline. */
    GstElement* pipeline;
    
    /* our gstreamer elements. */
    GstElement* source;
    GstElement* parse;
    GstElement* convert;
    GstElement* sample;
    GstElement* sink;
};

/*
 * private object definition.
 */
struct _GgnPresencePrivate {
    /* our tray icon. */
    GtkStatusIcon* icon;
    guint icon_style;
    gchar* icon_file;
    gchar* tip;
    
    /* our tray menu. */
    GladeXML* xml;
    GtkMenu* menu;
    
    /* our notification. */
    NotifyNotification* note;
    guint notify_style;
    gchar* notify_file;
    gchar* title;
    gchar* summary;
    NotifyUrgency urgency;
    
    /* our gstreamer structure. */
    GgnPresenceAudio gst;
};

/*
 * forward function definitions.
 */
static void ggn_presence_init (GgnPresence* self);
static void ggn_presence_class_init (GgnPresenceClass* klass);
static void ggn_presence_finalize (GObject* obj);

/*
 * define the gobject type and its basic functions.
 */
G_DEFINE_TYPE (GgnPresence, ggn_presence, G_TYPE_OBJECT);

/*
 * define the signals used.
 */
enum {
    SELECTED,
    CHECK_CLICKED,
    PREFS_CLICKED,
    ABOUT_CLICKED,
    QUIT_CLICKED,
    LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };

/*
 * ggn_presence_icon_check_clicked:
 *
 * This function is called as a callback when the user clicks
 * the "Check Mail" menu item in the tray icon menu.
 *
 * Return value: callback success.
 */
static gboolean ggn_presence_icon_check_clicked (GtkMenuItem* item,
                                                 gpointer data) {
    /* emit the "check_clicked" signal. */
    g_signal_emit (GGN_PRESENCE (data), signals[CHECK_CLICKED], 0);
    
    /* exit the function. */
    return TRUE;
}

/*
 * ggn_presence_icon_prefs_clicked:
 *
 * This function is called as a callback when the user clicks
 * the "Preferences" menu item in the tray icon menu.
 *
 * Return value: callback success.
 */
static gboolean ggn_presence_icon_prefs_clicked (GtkMenuItem* item,
                                                 gpointer data) {
    /* emit the "prefs_clicked" signal. */
    g_signal_emit (GGN_PRESENCE (data), signals[PREFS_CLICKED], 0);
    
    /* exit the function. */
    return TRUE;
}

/*
 * ggn_presence_icon_about_clicked:
 *
 * This function is called as a callback when the user clicks
 * the "About" menu item in the tray icon menu.
 *
 * Return value: callback success.
 */
static gboolean ggn_presence_icon_about_clicked (GtkMenuItem* item,
                                                 gpointer data) {
    /* emit the "about_clicked" signal. */
    g_signal_emit (GGN_PRESENCE (data), signals[ABOUT_CLICKED], 0);
    
    /* exit the function. */
    return TRUE;
}

/*
 * ggn_presence_icon_quit_clicked:
 *
 * This function is called as a callback when the user clicks
 * the "Quit" menu item in the tray icon menu.
 *
 * Return value: callback success.
 */
static gboolean ggn_presence_icon_quit_clicked (GtkMenuItem* item,
                                                 gpointer data) {
    /* emit the "quit_clicked" signal. */
    g_signal_emit (GGN_PRESENCE (data), signals[QUIT_CLICKED], 0);
    
    /* exit the function. */
    return TRUE;
}

/*
 * ggn_presence_icon_rclicked:
 *
 * This function is called as a callback when the user
 * right-clicks the tray icon.
 *
 * Return value: callback success.
 */
static gboolean ggn_presence_icon_rclicked (GtkWidget* widget,
                                            guint button,
                                            guint time,
                                            gpointer data) {
    /* grab a reference to the object. */
    GgnPresence* pres = GGN_PRESENCE (data);
    
    /* bring up the menu. */
    gtk_menu_popup (pres->priv->menu,
                    NULL,
                    NULL,
                    NULL,
                    NULL,
                    button,
                    time);
    
    /* exit the function. */
    return TRUE;
}

/*
 * ggn_presence_icon_lclicked:
 *
 * This function is called as a callback when the user
 * left-clicks the tray icon.
 *
 * Return value: callback success.
 */
static gboolean ggn_presence_icon_lclicked (GtkWidget *widget,
                                            gpointer data) {
    /* emit the "selected" signal. */
    g_signal_emit (GGN_PRESENCE (data), signals[SELECTED], 0);
    
    /* exit the function. */
    return TRUE;
}

/*
 * ggn_presence_audio_bus_call:
 *
 * This function is called when the gstreamer message bus
 * sends information about the currently active stream.
 *
 * Return value: callback success.
 */
static gboolean ggn_presence_audio_bus_call (GstBus* bus, GstMessage* msg,
                                             gpointer data) {
    /* regain our structure from the data. */
    GgnPresence* pres = (GgnPresence*) data;
    
    /* determine the type of message sent. */
    switch (GST_MESSAGE_TYPE (msg)) {
        /* end of stream message. */
        case GST_MESSAGE_EOS: {
            /* end iteration of our audio loop. */
            gst_element_set_state (pres->priv->gst.pipeline, GST_STATE_NULL);
            g_main_loop_quit (pres->priv->gst.loop);
            
            /* leave the switch. */
            break;
        }
        
        /* error message. */
        case GST_MESSAGE_ERROR: {
            /* define some helping variables. */
            GError* err;
            gchar* dbg;
            
            /* get the error information. */
            gst_message_parse_error (msg, &err, &dbg);
            
            /* output the error to the terminal. */
            g_debug ("gstreamer error %d: %s\nLast outputs:\n%s", err->code,
                     err->message, dbg);
            
            /* free the error variables. */
            g_error_free (err);
            g_free (dbg);
            
            /* end iteration of the audio loop. */
            gst_element_set_state (pres->priv->gst.pipeline, GST_STATE_NULL);
            g_main_loop_quit (pres->priv->gst.loop);
            
            /* leave the switch. */
            break;
        }
        
        /* anything else. */
        default: {
            /* leave the switch. */
            break;
        }
    }
    
    /* return success. */
    return TRUE;
}

/*
 * ggn_presence_audio_pad_added:
 *
 * Becuase elements in our pipeline utilize dynamic pads, a callback
 * must be set up to handle their initialization in a special way.
 * This function finalizes the linkage in the pipeline.
 *
 * Return value: void.
 */
static void ggn_presence_audio_pad_added (GstElement* element, GstPad* pad,
                                          gpointer data) {
    /* pull our audioconvert element out of the data. */
    GstElement* convert = (GstElement*) data;
    
    /* define the sink pad. */
    GstPad* sinkpad;
    
    /* get the dynamic pad for the end of the chain. */
    sinkpad = gst_element_get_pad (convert, "sink");
    
    /* link the two pads together. */
    gst_pad_link (pad, sinkpad);
    
    /* unreference our sinkpad, as it's no longer needed. */
    gst_object_unref (sinkpad);
}

/*
 * ggn_presence_default_selected_cb:
 *
 * This is the default "selected" callback function manager.
 *
 * Return value: void.
 */
static void ggn_presence_default_selected_cb (GgnPresence* pres) {
    /* exit the function. */
    return;
}

/*
 * ggn_presence_default_check_clicked_cb:
 *
 * This is the default "check_clicked" callback function manager.
 *
 * Return value: void.
 */
static void ggn_presence_default_check_clicked_cb (GgnPresence* pres) {
    /* exit the function. */
    return;
}

/*
 * ggn_presence_default_prefs_clicked_cb:
 *
 * This is the default "prefs_clicked" callback function manager.
 *
 * Return value: void.
 */
static void ggn_presence_default_prefs_clicked_cb (GgnPresence* pres) {
    /* exit the function. */
    return;
}

/*
 * ggn_presence_default_about_clicked_cb:
 *
 * This is the default "about_clicked" callback function manager.
 *
 * Return value: void.
 */
static void ggn_presence_default_about_clicked_cb (GgnPresence* pres) {
    /* exit the function. */
    return;
}

/*
 * ggn_presence_default_quit_clicked_cb:
 *
 * This is the default "quit_clicked" callback function manager.
 *
 * Return value: void.
 */
static void ggn_presence_default_quit_clicked_cb (GgnPresence* pres) {
    /* exit the function. */
    return;
}

/*
 * ggn_presence_init:
 *
 * This function is used by the gobject library to
 * generate a new instance of our object.
 */
static void ggn_presence_init (GgnPresence* self) {
    /* set up the private data structure. */
    self->priv = g_new0 (GgnPresencePrivate, 1);
    
    /* create the tray icon. */
    self->priv->icon = gtk_status_icon_new ();
    
    /* link the tray icon to its left-clicked callback. */
    g_signal_connect (G_OBJECT (self->priv->icon),
                      "popup-menu",
                      G_CALLBACK (ggn_presence_icon_rclicked),
                      self);
    
    /* link the tray icon to its right-clicked callback. */
    g_signal_connect (G_OBJECT (self->priv->icon),
                      "activate",
                      G_CALLBACK (ggn_presence_icon_lclicked),
                      self);
    
    /* locate the glade xml file. */
    gchar* filename = ggn_glade_file (GGN_GLADE_MENU);
    GtkImageMenuItem* item;
    
    /* make the menu. */
    self->priv->xml = glade_xml_new (filename, NULL, NULL);
    self->priv->menu = (GtkMenu*) glade_xml_get_widget (self->priv->xml,
                                                        "GgnContextMenu");
    
    /* link the menu item into our clicked callback. */
    item = (GtkImageMenuItem*) glade_xml_get_widget (self->priv->xml,
                                                     "itemCheck");
    g_signal_connect (G_OBJECT (item),
                      "activate",
                      G_CALLBACK (ggn_presence_icon_check_clicked),
                      self);
    
    /* link the menu item into our clicked callback. */
    item = (GtkImageMenuItem*) glade_xml_get_widget (self->priv->xml,
                                                     "itemPrefs");
    g_signal_connect (G_OBJECT (item),
                      "activate",
                      G_CALLBACK (ggn_presence_icon_prefs_clicked),
                      self);
    
    /* link the menu item into our clicked callback. */
    item = (GtkImageMenuItem*) glade_xml_get_widget (self->priv->xml,
                                                     "itemAbout");
    g_signal_connect (G_OBJECT (item),
                      "activate",
                      G_CALLBACK (ggn_presence_icon_about_clicked),
                      self);
    
    /* link the menu item into our clicked callback. */
    item = (GtkImageMenuItem*) glade_xml_get_widget (self->priv->xml,
                                                     "itemQuit");
    g_signal_connect (G_OBJECT (item),
                      "activate",
                      G_CALLBACK (ggn_presence_icon_quit_clicked),
                      self);
    
    /* free the file string. */
    g_free (filename);
    
    /* setup our audio loop. */
    self->priv->gst.loop = g_main_loop_new (NULL, FALSE);
    
    /* create the gstreamer pipeline. */
    self->priv->gst.pipeline = gst_pipeline_new (PACKAGE);
    
    /* create the file source element. */
    self->priv->gst.source = gst_element_factory_make ("filesrc", "source");
    
    /* create the wav parser element. */
    self->priv->gst.parse = gst_element_factory_make ("wavparse", "parse");
    
    /* create the file source element. */
    self->priv->gst.convert = gst_element_factory_make ("audioconvert",
                                                        "convert");
    
    /* create the file source element. */
    self->priv->gst.sample = gst_element_factory_make ("audioresample",
                                                       "sample");
    
    /* create the file source element. */
    self->priv->gst.sink = gst_element_factory_make ("autoaudiosink", "sink");
    
    /* add a bus watch for important messages. */
    GstBus* bus = gst_pipeline_get_bus (GST_PIPELINE (self->priv->gst.pipeline));
    gst_bus_add_watch (bus, ggn_presence_audio_bus_call, self);
    gst_object_unref (bus);
    
    /* add all the elements into the pipeline. */
    gst_bin_add_many (GST_BIN (self->priv->gst.pipeline),
                      self->priv->gst.source, self->priv->gst.parse,
                      self->priv->gst.convert, self->priv->gst.sample,
                      self->priv->gst.sink, NULL);
    
    /* link the source and sink ends together. */
    gst_element_link (self->priv->gst.source, self->priv->gst.parse);
    gst_element_link_many (self->priv->gst.convert, self->priv->gst.sample,
                           self->priv->gst.sink, NULL);
    
    /* use a callback to link the two chains. */
    g_signal_connect (G_OBJECT (self->priv->gst.parse),
                      "pad-added",
                      G_CALLBACK (ggn_presence_audio_pad_added),
                      self->priv->gst.convert);
}

/*
 * ggn_presence_class_init:
 *
 * This function is used by the gobject library to
 * generate a new class object of our object.
 */
static void ggn_presence_class_init (GgnPresenceClass* klass) {
    /* setup a gobject class. */
    GObjectClass* gobj_class = G_OBJECT_CLASS (klass);
    
    /* set the locations of our destruction function. */
    gobj_class->finalize = ggn_presence_finalize;
    
    /* setup the default signal handlers. */
    klass->selected = ggn_presence_default_selected_cb;
    klass->check_clicked = ggn_presence_default_check_clicked_cb;
    klass->prefs_clicked = ggn_presence_default_prefs_clicked_cb;
    klass->about_clicked = ggn_presence_default_about_clicked_cb;
    klass->quit_clicked = ggn_presence_default_quit_clicked_cb;
    
    /*
     * GgnPresence::selected:
     *
     * Emitted when the user left-clicks the tray icon.
     */
    signals[SELECTED] = g_signal_new ("selected",
                                      G_OBJECT_CLASS_TYPE (gobj_class),
                                      G_SIGNAL_RUN_FIRST,
                                      G_STRUCT_OFFSET (GgnPresenceClass,
                                                       selected),
                                      NULL, NULL,
                                      ggn_marshal_VOID__VOID,
                                      G_TYPE_NONE, 0);
    
    /*
     * GgnPresence::check_clicked:
     *
     * Emitted when the user opens the tray icon's right-click
     * (context) menu and selects the "Check Mail" option.
     */
    signals[CHECK_CLICKED] = g_signal_new ("check_clicked",
                                           G_OBJECT_CLASS_TYPE (gobj_class),
                                           G_SIGNAL_RUN_FIRST,
                                           G_STRUCT_OFFSET (GgnPresenceClass,
                                                            check_clicked),
                                           NULL, NULL,
                                           ggn_marshal_VOID__VOID,
                                           G_TYPE_NONE, 0);
    
    /*
     * GgnPresence::prefs_clicked:
     *
     * Emitted when the user opens the tray icon's right-click
     * (context) menu and selects the "Preferences" option.
     */
    signals[PREFS_CLICKED] = g_signal_new ("prefs_clicked",
                                           G_OBJECT_CLASS_TYPE (gobj_class),
                                           G_SIGNAL_RUN_FIRST,
                                           G_STRUCT_OFFSET (GgnPresenceClass,
                                                            prefs_clicked),
                                           NULL, NULL,
                                           ggn_marshal_VOID__VOID,
                                           G_TYPE_NONE, 0);
    
    /*
     * GgnPresence::about_clicked:
     *
     * Emitted when the user opens the tray icon's right-click
     * (context) menu and selects the "About" option.
     */
    signals[ABOUT_CLICKED] = g_signal_new ("about_clicked",
                                           G_OBJECT_CLASS_TYPE (gobj_class),
                                           G_SIGNAL_RUN_FIRST,
                                           G_STRUCT_OFFSET (GgnPresenceClass,
                                                            about_clicked),
                                           NULL, NULL,
                                           ggn_marshal_VOID__VOID,
                                           G_TYPE_NONE, 0);
    
    /*
     * GgnPresence::quit_clicked:
     *
     * Emitted when the user opens the tray icon's right-click
     * (context) menu and selects the "Quit" option.
     */
    signals[QUIT_CLICKED] = g_signal_new ("quit_clicked",
                                          G_OBJECT_CLASS_TYPE (gobj_class),
                                          G_SIGNAL_RUN_FIRST,
                                          G_STRUCT_OFFSET (GgnPresenceClass,
                                                           quit_clicked),
                                          NULL, NULL,
                                          ggn_marshal_VOID__VOID,
                                          G_TYPE_NONE, 0);
}

/*
 * ggn_presence_finalize:
 *
 * This function is used by the gobject library to cleanly finish
 * the destruction process started by the dispose function.
 */
static void ggn_presence_finalize (GObject* obj) {
    /* make a reference to ourself. */
    GgnPresence* self = GGN_PRESENCE (obj);
    
    /* free the icon. */
    g_object_unref (G_OBJECT (self->priv->icon));
    
    /* free the strings. */
    g_free (self->priv->icon_file);
    g_free (self->priv->tip);
    g_free (self->priv->notify_file);
    g_free (self->priv->title);
    g_free (self->priv->summary);
    
    /* free the menu glade xml. */
    g_object_unref (G_OBJECT (self->priv->xml));
    gtk_widget_destroy (GTK_WIDGET (self->priv->menu));
    
    /* destroy the private object. */
    g_free (self->priv);
    self->priv = NULL;
    
    /* chain up to the parent class. */
    G_OBJECT_CLASS (ggn_presence_parent_class)->finalize (obj);
}

/*
 * ggn_presence_new:
 *
 * Creates a new GgnPresence object for use in the application.
 * It maintains a presence on the panel, with sound effects,
 * and with libnotify notifications.
 *
 * Return value: the new presence object.
 */
GgnPresence* ggn_presence_new (void) {
    /* make a newly created gobject. */
    GgnPresence* pres = g_object_new (GGN_TYPE_PRESENCE, NULL);
    
    /* return the new object. */
    return pres;
}

/*
 * ggn_presence_free:
 *
 * Frees the given presence object by decreasing its reference count.
 *
 * Return value: void.
 */
void ggn_presence_free (GgnPresence* pres) {
    /* unreference the object. */
    ggn_presence_finalize (G_OBJECT (pres));
}

/*
 * ggn_presence_set_icon_style:
 *
 * Sets the style of the tray icon used by the application.
 *
 * Return value: void.
 */
void ggn_presence_set_icon_style (GgnPresence* pres, guint style) {
    /* set the style variable. */
    pres->priv->icon_style = style;
    
    /* see if we free the old filename. */
    g_free (pres->priv->icon_file);
    
    /* see what style was provided. */
    if (style == GGN_PRESENCE_STYLE_NORMAL) {
        /* generate the filename string. */
        pres->priv->icon_file = ggn_pixmap_file (GGN_PIXMAP_NORMAL);
    }
    else if (style == GGN_PRESENCE_STYLE_ERROR) {
        /* generate the filename string. */
        pres->priv->icon_file = ggn_pixmap_file (GGN_PIXMAP_ERROR);
    }
    else if (style == GGN_PRESENCE_STYLE_MESSAGE) {
        /* generate the filename string. */
        pres->priv->icon_file = ggn_pixmap_file (GGN_PIXMAP_MESSAGE);
    }
    else if (style == GGN_PRESENCE_STYLE_CHECKING) {
        /* generate the filename string. */
        pres->priv->icon_file = ggn_pixmap_file (GGN_PIXMAP_CHECKING);
    }
    else if (style == GGN_PRESENCE_STYLE_WARNING) {
        /* generate the filename string. */
        pres->priv->icon_file = ggn_pixmap_file (GGN_PIXMAP_WARNING);
    }
    
    /* load the pixmap for the icon. */
    gtk_status_icon_set_from_file (pres->priv->icon,
                                   pres->priv->icon_file);
}

/*
 * ggn_presence_set_icon_tip:
 *
 * Sets the tray icon's tooltip text value.
 *
 * Return value: void.
 */
void ggn_presence_set_icon_tip (GgnPresence* pres, gchar* text) {
    /* see if we free the tip text. */
    g_free (pres->priv->tip);
    
    /* set the new tip. */
    pres->priv->tip = g_strdup (text);
    
    /* update gtk's tip. */
    gtk_status_icon_set_tooltip (pres->priv->icon, text);
}

/*
 * ggn_presence_set_icon_visible:
 *
 * Sets whether or not the tray icon is visible to the
 * user on the panel.
 *
 * Return value: void.
 */
void ggn_presence_set_icon_visible (GgnPresence* pres, gboolean vis) {
    /* just tell gtk what to do. */
    gtk_status_icon_set_visible (pres->priv->icon, vis);
}

/*
 * ggn_presence_set_notify_style:
 *
 * Sets the style of the tray notifications used by the application.
 *
 * Return value: void.
 */
void ggn_presence_set_notify_style (GgnPresence* pres, guint style) {
    /* set the style variable. */
    pres->priv->notify_style = style;
    
    /* see if we free the old filename. */
    g_free (pres->priv->notify_file);
    
    /* see what style was provided. */
    if (style == GGN_PRESENCE_STYLE_NORMAL) {
        /* set the urgency. */
        pres->priv->urgency = NOTIFY_URGENCY_NORMAL;
        
        /* generate the filename string. */
        pres->priv->notify_file = ggn_pixmap_file (GGN_PIXMAP_NORMAL);
    }
    else if (style == GGN_PRESENCE_STYLE_ERROR) {
        /* set the urgency. */
        pres->priv->urgency = NOTIFY_URGENCY_CRITICAL;
        
        /* generate the filename string. */
        pres->priv->notify_file = ggn_pixmap_file (GGN_PIXMAP_ERROR);
    }
    else if (style == GGN_PRESENCE_STYLE_MESSAGE) {
        /* set the urgency. */
        pres->priv->urgency = NOTIFY_URGENCY_LOW;
        
        /* generate the filename string. */
        pres->priv->notify_file = ggn_pixmap_file (GGN_PIXMAP_MESSAGE);
    }
    else if (style == GGN_PRESENCE_STYLE_CHECKING) {
        /* set the urgency. */
        pres->priv->urgency = NOTIFY_URGENCY_NORMAL;
        
        /* generate the filename string. */
        pres->priv->notify_file = ggn_pixmap_file (GGN_PIXMAP_CHECKING);
    }
    else if (style == GGN_PRESENCE_STYLE_WARNING) {
        /* set the urgency. */
        pres->priv->urgency = NOTIFY_URGENCY_NORMAL;
        
        /* generate the filename string. */
        pres->priv->notify_file = ggn_pixmap_file (GGN_PIXMAP_WARNING);
    }
}

/*
 * ggn_presence_set_notify_title:
 *
 * Sets the title text (called "summary text" by the libnotify API)
 * to the text provided as a string to the function.
 *
 * Return value: void.
 */
void ggn_presence_set_notify_title (GgnPresence* pres, gchar* text) {
    /* see if we free the title text. */
    g_free (pres->priv->title);
    
    /* set the new title. */
    pres->priv->title = g_strdup (text);
}

/*
 * ggn_presence_set_notify_summary:
 *
 * Sets the summary text (called "body text" by the libnotify API)
 * to the text provided as a string to the function.
 *
 * Return value: void.
 */
void ggn_presence_set_notify_summary (GgnPresence* pres, gchar* text) {
    /* see if we free the summary text. */
    g_free (pres->priv->summary);
    
    /* set the new summary. */
    pres->priv->summary = g_strdup (text);
}

/*
 * ggn_presence_show_notify_thread:
 *
 * This function is the function responsible for waiting to show
 * the notification until the panel "tray" icon is embedded. The
 * old notifier versions had no method of coping with an un-embedded
 * tray icon, and would show notifications in random locations as a
 * result.
 *
 * Return value: a pointer to the given data.
 */
gpointer ggn_presence_show_notify_thread (gpointer data) {
    /* lock the gtk thread. */
    gdk_threads_enter ();
    
    /* get the reference to our presence object. */
    GgnPresence* pres = GGN_PRESENCE (data);
    
    /* wait until the icon is embedded. */
    while (!gtk_status_icon_is_embedded (pres->priv->icon)) {
        /* wait a bit. */
        if (gtk_events_pending ()) {
            /* let gtk run an iteration. */
            gtk_main_iteration ();
        }
    }
    
    /* create a new notification. */
    pres->priv->note = notify_notification_new (pres->priv->title,
                                                pres->priv->summary,
                                                pres->priv->notify_file,
                                                NULL);
    
    /* attach to notification to our icon. */
    notify_notification_attach_to_status_icon (pres->priv->note,
                                               pres->priv->icon);
    
    /* set the notification timeout. */
    notify_notification_set_timeout (pres->priv->note, (4 * 1000));
    
    /* set the notification urgency. */
    notify_notification_set_urgency (pres->priv->note, pres->priv->urgency);
    
    /* show the notification. */
    notify_notification_show (pres->priv->note, NULL);
    
    /* unlock the gtk thread. */
    gdk_threads_leave ();
    
    /* return the data. */
    return data;
}

/*
 * ggn_presence_show_notify:
 *
 * Creates a new thread to show a notification to the
 * user.
 *
 * Return value: void.
 */
void ggn_presence_show_notify (GgnPresence* pres) {
    /* ensure that libnotify is connected to our program. */
    if (!notify_is_initted ()) {
        /* init libnotify. */
        notify_init (g_get_prgname ());
    }
    
    /* create the thread to run, and execute it. */
    g_thread_create (ggn_presence_show_notify_thread,
                     (gpointer) pres, FALSE, NULL);
}

/*
 * ggn_presence_play_sound_thread:
 *
 * A thread used by the ggn_presence_play_sound function in order to
 * play all audio in a separate thread.
 *
 * Return value: thread data.
 */
gpointer ggn_presence_play_sound_thread (gpointer data) {
    /* get our presence object. */
    GgnPresence* pres = (GgnPresence*) data;
    
    /* set the pipeline to playing and kickoff the audio loop. */
    gst_element_set_state (pres->priv->gst.pipeline, GST_STATE_PLAYING);
    g_main_loop_run (pres->priv->gst.loop);
    
    /* quit the thread. */
    return data;
}

/*
 * ggn_presence_play_sound:
 *
 * Plays a sound file through the GNOME sound system, whichever
 * that may be at the time of execution.
 *
 * Return value: void.
 */
void ggn_presence_play_sound (GgnPresence* pres, const gchar* soundfile) {
    /* make sure we have a string. */
    if (soundfile == NULL) {
        /* exit the function. */
        return;
    }
    
    /* make sure we have a file. */
    if (g_file_test (soundfile, G_FILE_TEST_IS_REGULAR) == FALSE) {
        /* exit the function. */
        return;
    }
    
    /* set the location argument for the file source element. */
    g_object_set (G_OBJECT (pres->priv->gst.source), "location",
                  soundfile, NULL);
    
    /* spawn a thread to handle the sound playback. */
    g_thread_create (ggn_presence_play_sound_thread,
                     (gpointer) pres, FALSE, NULL);
}
